home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 February: Tool Chest / Apple Developer CD Series Tool Chest February 1996 (Apple Computer)(1996).iso / Sample Code / Snippets / QuickDraw / DTS Groupies / Groupies.c < prev   
Encoding:
C/C++ Source or Header  |  1992-07-15  |  20.0 KB  |  726 lines  |  [TEXT/KAHL]

  1. /*    ________________________________________________________
  2.     ________________________________________________________
  3.  
  4.     DTS Groupies - Dave Hersey 9/18/91 MacDTS
  5.    
  6.     This thingy groups a bunch of pictures together and
  7.     takes them apart using PicComments and a QuickDraw
  8.     bottleneck procedure.  It's an example of how to
  9.     store and retrieve custom PicComments, among other
  10.     things.
  11.    
  12.     Here's how it works:
  13.     
  14.     All pictures are kept in a global structure for easy
  15.     access.  Note that I use the same bounds rectangle
  16.     for all the pictures, but this isn't necessary.
  17.     You should make sure that the enclosing picture is
  18.     wide enough to hold all the others though, or else
  19.     you'll get clipping when they're displayed.
  20.     
  21.     The global structure contains:
  22.     
  23.         • The no. of pictures currently stored.
  24.         • The PicHandles for each of those pictures.
  25.         • The last rectangle each picture was drawn
  26.           in.  (For erase and redraw procedures.)
  27.           
  28.     First, the app creates some individual pictures of
  29.     various objects.  Then it repeatedly groups and
  30.     ungroups them until the user chooses Quit from the
  31.     File menu.
  32.    
  33.     For demonstration purposes, the app moves the pictures
  34.     around as it alternates between grouped and ungrouped
  35.     stages.  This just shows the user that we really have
  36.     separate or consolidated pictures, as the case may be.
  37.     
  38.     Good stuff to look for:
  39.     
  40.         • Example of custom PicComment usage with unique
  41.           identifiers as in Tech Note #181.
  42.           {-> GroupiesPicProc, AssembleGroupies <-}
  43.     
  44.         • Example of a custom QuickDraw bottleneck procedure
  45.           which works in both GrafPorts and CGrafPorts.
  46.           {-> DisassembleGroupies <-}
  47.  
  48.         • Example of finding the deepest device and placing
  49.           a window on it.
  50.           {-> ShowTheGroupies <-}
  51.  
  52.     Lastly, my apologies for all the bad "groupie humor."
  53.  
  54.     {Any similarities to, or resemblance of, actual DTS
  55.      Groupies living or dead is purely coincidental and
  56.      unintentional.}
  57.     ________________________________________________________
  58.     ________________________________________________________    */
  59.  
  60.  
  61. /*________________Constants___________________*/
  62.  
  63.  
  64. #define kCreatorType    'EGAD'        /* Our creator type.                */
  65.  
  66.  
  67. #define rMenuBar        128            /* The menubar resource ID.            */
  68.  
  69. #define mApple            128            /* Apple menu ID.                    */
  70.  
  71. #define iAbout            1            /* "About…" menu item index.        */
  72.  
  73. #define mFile            129            /* File menu ID.                    */
  74.  
  75. #define iQuit            1            /* Quit menu item index.            */
  76.  
  77.  
  78. #define kMaxPICTs        50            /* Max. no. of pictures we handle.    */
  79.  
  80. #define kCustomComment    100            /* Custom PicComment indicator.        */
  81.  
  82. #define kSubPICTComment    200            /* Our (sub-picture) sub-PicComment.
  83.                                        This comment indicates that we've
  84.                                        stored a picture inside of a
  85.                                        picture.  We use it to extract
  86.                                        the individual PICTs.            */
  87.  
  88.  
  89. /*__________________Types_____________________*/
  90.  
  91.  
  92. typedef struct TPICTRec {
  93.     int            numPICTs;            /* The number of sub-pictures,         */
  94.     PicHandle    picture[kMaxPICTs];    /* and their PicHandles,            */
  95.     Rect        curPos[kMaxPICTs];    /* and their last drawn positions.    */
  96. } TPICTRec;
  97.  
  98.  
  99. /*_________________Globals____________________*/
  100.  
  101.  
  102. static TPICTRec        gPICTRec;        /* Our global picture record.        */
  103. static Rect            gGroupieBounds;    /* The bounds used by our pictures.    */
  104. static Boolean        gQuitting;        /* "Quitting?" flag.                */
  105. static WindowPtr    gTheWindow;        /* Our window's pointer.            */
  106.  
  107.  
  108. /*_______________Prototypes___________________*/
  109.  
  110. extern void            AssembleGroupies(void);
  111. extern pascal void    GroupiesPicProc(int kind, int dataSize, Handle dataHandle);
  112. extern void            DisassembleGroupies(void);
  113. extern void            DoMenuCommand(long menuResult);
  114. extern void            GetGroupieEvents(void);
  115. extern void            MoveTheGroupies(Rect *wBounds);
  116. extern void            MakeTheGroupies(void);
  117. extern void            ShowTheGroupies(void);
  118.  
  119.  
  120.  
  121. /*    -------------------------------------
  122.     AssembleGroupies groups all the
  123.     pictures in the global picture record
  124.     into one "composite" picture.  It
  125.     removes all the old pictures and
  126.     stores the new one.
  127.     -------------------------------------    */
  128.  
  129. void AssembleGroupies()
  130. {
  131.     PicHandle    aPICT, groupPICT;
  132.     RgnHandle    oldClip;
  133.     int            idx;
  134.     long        dataSize;
  135.     long        ownerApp;
  136.     short        localPicComment;
  137.  
  138. /*    Save the old clipping region, and set a valid one so our groupie
  139.     picture develops ok.                                                */
  140.  
  141.     oldClip = NewRgn();
  142.     GetClip(oldClip);
  143.     ClipRect(&gGroupieBounds);
  144.  
  145.     groupPICT = OpenPicture(&gGroupieBounds);
  146.  
  147.  
  148. /*    Create a picture to contain all the other ones, then draw those into
  149.     it, separated by our PicComments.  Kill the individual pictures as
  150.     we go.  Finally, close the composite picture.                        */
  151.  
  152.     ownerApp = kCreatorType;
  153.     localPicComment = kSubPICTComment;
  154.  
  155.     for(idx = 0; idx < gPICTRec.numPICTs; idx++)
  156.     {
  157.         aPICT = gPICTRec.picture[idx];
  158.  
  159.  
  160. /*    We don't just use a single custom PicComment since another app may
  161.     use the same comment and conflicts could result.  (Not in this app,
  162.     but in the real world.)  Instead, we use the method documented in
  163.     Technical Note #181.  We add six bytes to the handle and store the
  164.     creator type of the app that made the picture followed by 2 bytes
  165.     for a local PicComment kind within the app.  If we used more than
  166.     one PicComment in this app, this extra information would be
  167.     necessary.                                                            */
  168.  
  169.         dataSize = GetHandleSize((Handle) aPICT) +6;
  170.         SetHandleSize((Handle) aPICT, dataSize);
  171.  
  172.         BlockMove((Ptr) *aPICT, (Ptr) *aPICT +6, dataSize -6);
  173.         BlockMove(&ownerApp, (Ptr) *aPICT, 4);
  174.         BlockMove(&localPicComment, (Ptr) *aPICT +4, 2);
  175.  
  176.         PicComment(kCustomComment, dataSize, (Handle) aPICT);
  177.  
  178. /*    Fix the original PicHandle so that we can draw our picture for apps
  179.     that don't know about our custom comments.                            */
  180.  
  181.         BlockMove((Ptr) *aPICT +6, (Ptr) *aPICT, dataSize -6);
  182.         SetHandleSize((Handle) aPICT, dataSize -6);
  183.         DrawPicture(aPICT, &(*aPICT)->picFrame);
  184.         KillPicture(aPICT);
  185.         gPICTRec.picture[idx] = NULL;
  186.     }
  187.  
  188.     ClosePicture();
  189.  
  190.  
  191. /*    Restore the original clipping region and update our global picture
  192.     record so that we have one consolidated picture, in the first slot.
  193.     We set it's current position to (0, 0, 0, 0) so that we don't waste
  194.     time erasing anything on the first draw.                            */
  195.  
  196.     SetClip(oldClip);
  197.     DisposeRgn(oldClip);
  198.  
  199.     gPICTRec.numPICTs = 1;
  200.     gPICTRec.picture[0] = groupPICT;
  201.     SetRect(&gPICTRec.curPos[0], 0, 0, 0, 0);
  202. }
  203.  
  204.  
  205. /*    -------------------------------------
  206.     GroupiesPicProc is our replacement
  207.     for the port's StdCommentProc.
  208.     -------------------------------------    */
  209.  
  210. pascal void GroupiesPicProc(int kind, int dataSize, Handle dataHandle)
  211. {
  212.     int            nextNum;
  213.     long        ownerApp;
  214.     short        localPicComment;
  215.     Handle        theHandle;
  216.  
  217. /*    If this is a custom PicComment, see if it's ours.  In this app,
  218.     we know it always will be, but when you import other pictures
  219.     you can't be so sure.                                                */
  220.  
  221.     if (kind == kCustomComment && (gPICTRec.numPICTs < kMaxPICTs))
  222.     {
  223.         if (dataSize < 6) return;                        /* Not ours?    */
  224.         
  225.         BlockMove((Ptr) *dataHandle, &ownerApp, 4);
  226.         BlockMove((Ptr) *dataHandle +4, &localPicComment, 2);
  227.  
  228.         if ((ownerApp != kCreatorType) ||                /* Not ours?    */
  229.             (localPicComment != kSubPICTComment)) return;
  230.  
  231.  
  232. /*    This is indeed our picture comment.  Create a handle for the data we
  233.     found, store it in our global picture record and bump the number of
  234.     pictures we have.  The reason that we clear the picture's curPos
  235.     rect is so that we won't waste time erasing anything the first time
  236.     we enter MoveTheGroupies.                                            */
  237.  
  238.         nextNum = gPICTRec.numPICTs;
  239.         gPICTRec.picture[nextNum] = (PicHandle) dataHandle;
  240.         SetRect(&gPICTRec.curPos[nextNum], 0, 0, 0, 0);
  241.  
  242.  
  243. /*    After we create the handle for the data, we have to remember that
  244.     we have 6 bytes of identifying "garbage" in front of the picture
  245.     data.  To remove that, BlockMove all the picture data to the
  246.     beginning of the handle and reset the handle's size.  This is kind
  247.     of a hassle, but it's really best to store your custom PicComments
  248.     this way.  Otherwise, you may misinterpret someone elses comments
  249.     or cause them to misinterpret yours.                                */
  250.  
  251.         if (HandToHand((Handle *) &gPICTRec.picture[nextNum]) == noErr)
  252.         {
  253.             ++gPICTRec.numPICTs;
  254.             theHandle = (Handle) gPICTRec.picture[nextNum];
  255.             BlockMove((Ptr) *theHandle +6, (Ptr) *theHandle, dataSize -6);
  256.             SetHandleSize(theHandle, dataSize -6);
  257.         }
  258.     }
  259. }
  260.  
  261.  
  262. /*    -------------------------------------
  263.     DisassembleGroupies ungroups the
  264.     first picture in the global picture
  265.     record.  It replaces that picture
  266.     with new pictures of every picture
  267.     it contained.  All drawing is done
  268.     within another "dummy" picture
  269.     so that nothing draws on the screen.
  270.     The reason we can't use an empty
  271.     clipping region to do this is that
  272.     PicComments will be clipped out along
  273.     with everything else, and we'd be
  274.     hosed.  (We need the PicComments!)
  275.     
  276.     This code is written so that it
  277.     installs the GrafProcs correctly for
  278.     both GrafPorts and CGrafPorts.
  279.     -------------------------------------    */
  280.  
  281. void DisassembleGroupies()
  282. {
  283.     GrafPtr        curPort;
  284.     QDProcs        theQDProcs;        /* If we're using a GrafPort…            */
  285.     CQDProcs    theCQDProcs;    /* If we're using a CGrafPort…            */
  286.     PicHandle    dummyPICT;
  287.  
  288. /*    Reset the number of pictures in our global picture record to zero.
  289.     There's actually one picture there at this point (the composite
  290.     one), but we must set this to zero so that our PicComment handler
  291.     stores extracted pictures in the right place.                        */
  292.  
  293.     gPICTRec.numPICTs = 0;
  294.  
  295.  
  296. /*    Get the current port and the standard QDProcs or CQDProcs,
  297.     depending on whether we have a GrafPort or CGrafPort.                */
  298.  
  299.     GetPort(&curPort);
  300.  
  301.     if (curPort->portBits.rowBytes < 0)                /* CGrafPort…        */
  302.     {
  303.         SetStdCProcs(&theCQDProcs);
  304.         theCQDProcs.commentProc = (Ptr) &GroupiesPicProc;
  305.         curPort->grafProcs = (QDProcsPtr) &theCQDProcs;
  306.     }
  307.     else                                            /* GrafPort…        */
  308.     {
  309.         SetStdProcs(&theQDProcs);
  310.         theQDProcs.commentProc = (Ptr) &GroupiesPicProc;
  311.         curPort->grafProcs = (QDProcsPtr) &theQDProcs;
  312.     }
  313.  
  314.  
  315. /*    Open our dummy picture and draw into it so that our PicComment
  316.     handler is called to parse the picture.  When finished, close the
  317.     picture, kill it and remove our grafProcs.                            */
  318.  
  319.     dummyPICT = OpenPicture(&(*gPICTRec.picture[0])->picFrame);
  320.     DrawPicture(gPICTRec.picture[0], &(*gPICTRec.picture[0])->picFrame);
  321.     ClosePicture();
  322.     KillPicture(dummyPICT);
  323.  
  324.     curPort->grafProcs = NULL;
  325. }
  326.  
  327.  
  328. /*    -------------------------------------
  329.     DoMenuCommand handles our menu items.
  330.     -------------------------------------    */
  331.  
  332. void DoMenuCommand(long menuResult)
  333. {
  334.     int            menuID, menuItem;
  335.     Str255        daName;
  336.     MenuHandle    theMenu;
  337.     GrafPtr        savePort;
  338.  
  339. /*    Get the menu ID and item ID.            */
  340.  
  341.     menuID = (menuResult >>16) & 0xFFFF;
  342.     menuItem = menuResult & 0xFFFF;
  343.  
  344.  
  345. /*    Do what we're supposed to.                */
  346.  
  347.     switch (menuID)
  348.     {
  349.         case mApple:                        /*    Apple Menu                    */
  350.             switch (menuItem)
  351.             {
  352.                 case iAbout:                /*    -> Handle "About…"            */
  353.                     break;
  354.  
  355.                 default:                    /*    -> The rest are DAs.        */
  356.  
  357.                     GetPort(&savePort);
  358.                     GetItem(GetMHandle(mApple), menuItem, (ConstStr255Param) daName);
  359.                     OpenDeskAcc(daName);
  360.                     SetPort(savePort);
  361.                     break;
  362.             };
  363.  
  364.         case mFile:                            /*    File Menu                    */
  365.             switch (menuItem)
  366.             {
  367.                 case iQuit:
  368.                     gQuitting = true;        /*    -> Quit                        */
  369.                     break;
  370.             }
  371.  
  372.     };
  373.  
  374.     HiliteMenu(0);
  375.  
  376. };
  377.  
  378.  
  379. /*    -------------------------------------
  380.     GetGroupieEvents is a main event
  381.     loop.  It calls WaitNextEvent and
  382.     other nice stuff.  It also makes our
  383.     Groupies assemble, disassemble, and
  384.     hop about.
  385.     -------------------------------------    */
  386.  
  387. void GetGroupieEvents()
  388. {
  389.     EventRecord        theEvent;
  390.     WindowPtr        whichWindow;
  391.     short            partCode, idx;
  392.     Rect            dragRect;
  393.     RgnHandle        grayRgn;
  394.     char            key, time;
  395.     long            finalTicks;
  396.  
  397.  
  398. /*    Set up the rectangle for where we can drag windows.  Initialize
  399.     our "time-through-the-loop" counter to -1 so that it gets bumped
  400.     to zero on the first pass.  This will enable us assemble the
  401.     grouped picture as we go through the first time.                        */
  402.  
  403.     grayRgn = GetGrayRgn();
  404.     dragRect = (*grayRgn)->rgnBBox;
  405.     time = -1;
  406.  
  407.  
  408. /*    We have a counter which goes from 0-26 and is incremented each time
  409.     we go through this code. At time = 0, We assemble the grouped image.
  410.     At time = 12, we break all the PICTs out of it.  At time = 27, we
  411.     cycle back to time = 0.  In between these life altering times,
  412.     (at least for groupies), we draw all of our current pictures in
  413.     random places.  This clearly shows whether the PICTs are currently
  414.     grouped or not.  We go through this loop until the user quits.            */
  415.  
  416.     do
  417.     {
  418.         SetPort(gTheWindow);
  419.         time = ++time % 27;
  420.     
  421.          if (time == 0)
  422.         {
  423.             AssembleGroupies();            /*    Group the pictures.                */
  424.             EraseRect(&(gTheWindow)->portRect);
  425.         }
  426.  
  427.          if (time == 12)
  428.         {
  429.             DisassembleGroupies();        /*    Ungroup the pictures.            */
  430.             EraseRect(&(gTheWindow)->portRect);
  431.         }
  432.  
  433.  
  434. /*    Move all pictures so we can see their current state.                    */
  435.  
  436.         MoveTheGroupies(&(gTheWindow)->portRect);
  437.  
  438.  
  439. /*    Delay so our graphics don't flash.                                        */
  440.  
  441.         Delay((time < 12)? 40:10, &finalTicks);
  442.     
  443.  
  444. /*    Handle any pending events.                                                */
  445.  
  446.         if (WaitNextEvent(everyEvent, &theEvent, 0, NULL))
  447.             switch (theEvent.what)
  448.             {
  449.                 case mouseDown:                    /*    Handle mouse clicks.    */
  450.                 
  451.                     partCode = FindWindow(theEvent.where, &whichWindow);
  452.  
  453.                     switch (partCode)
  454.                     {
  455.                         case inContent:
  456.                             if (whichWindow != FrontWindow())
  457.                                 SelectWindow(whichWindow);
  458.                             break;
  459.     
  460.                         case inDrag:
  461.                             DragWindow(whichWindow, theEvent.where, &dragRect);
  462.                             break;
  463.     
  464.                         case inMenuBar:
  465.                             DoMenuCommand(MenuSelect(theEvent.where));
  466.     
  467.                         case inSysWindow:
  468.                             SystemClick(&theEvent, whichWindow);
  469.                             break;
  470.                     }
  471.                     break;
  472.     
  473.                 case updateEvt:                    /*    Handle update events.    */
  474.                         BeginUpdate((WindowPtr) theEvent.message);
  475.                         EndUpdate((WindowPtr) theEvent.message);
  476.                     break;
  477.  
  478.                 case keyDown:                    /*    Handle key presses.        */
  479.                 case autoKey: 
  480.                     key = (char) (theEvent.message & charCodeMask);
  481.                     if (((theEvent.modifiers & cmdKey) != 0) && (theEvent.what == keyDown))
  482.                         DoMenuCommand(MenuKey(key));
  483.                     break;
  484.             }
  485.     }
  486.     while (!gQuitting);
  487. }
  488.  
  489.  
  490. /*    -------------------------------------
  491.     MoveTheGroupies moves the current
  492.     pictures… somewhere randomly.  It
  493.     first erases all the pictures in
  494.     descending order.  Then it redraws
  495.     them in new locations in ascending
  496.     order. This way we don't wipe out
  497.     any of the new pictures when the old
  498.     ones are erased.
  499.     -------------------------------------    */
  500.  
  501. void MoveTheGroupies(Rect *wBounds)
  502. {
  503.     int        newLeft, newTop, width, height, idx;
  504.     float    maxX, maxY;
  505.     Rect    picFrame, curPos;
  506.  
  507. /*    First erase all pictures in reverse order.  Also, calculate their
  508.     new locations and store those in their curPos fields.                */
  509.  
  510.     for (idx = gPICTRec.numPICTs -1; idx >= 0; idx--)
  511.     {
  512.         curPos = gPICTRec.curPos[idx];
  513.         EraseRect(&curPos);
  514.  
  515.         picFrame = (*gPICTRec.picture[idx])->picFrame;
  516.         width = picFrame.right -picFrame.left;
  517.         height = picFrame.bottom -picFrame.top;
  518.  
  519.  
  520. /*    To calculate new positions, we find the maximum position we can
  521.     have for the picture's top left corner.  Then, we find a random
  522.     point that's bounded by (0, 0) and that maximum.  Finally, we
  523.     set this picture's current position so that it has this point for
  524.     its top left corner.                                                */
  525.  
  526.         maxX = (wBounds->right - wBounds->left) -width;
  527.         maxY = (wBounds->bottom - wBounds->top) -height;
  528.         
  529.         newTop = (((float) Random() +32767)/65534.0) * maxX;
  530.         newLeft = (((float) Random() +32767)/65534.0) * maxY;
  531.     
  532.         curPos.top = newTop;
  533.         curPos.left = newLeft;
  534.         curPos.bottom = newTop +height;
  535.         curPos.right = newLeft +width;
  536.         gPICTRec.curPos[idx] = curPos;
  537.     }
  538.  
  539.  
  540. /*    Now draw all the pictures in their new positions.                    */
  541.  
  542.     for (idx = 0; idx < gPICTRec.numPICTs; idx++)
  543.         DrawPicture(gPICTRec.picture[idx], &gPICTRec.curPos[idx]);
  544. }
  545.  
  546.  
  547. /*    -------------------------------------
  548.     MakeTheGroupies creates the pictures
  549.     that will be grouped.  These can be
  550.     any QuickDraw pictures.  For this
  551.     example, I use four pictures; one
  552.     containing a square, one with a
  553.     circle, one with a triangle and one
  554.     with some text.  These are all stored
  555.     in the global picture record.
  556.     
  557.     This routine is only called once, to
  558.     put some pictures into the works to
  559.     start with.
  560.     -------------------------------------    */
  561.  
  562. void MakeTheGroupies()
  563. {
  564.     RgnHandle    oldClip;
  565.     PolyHandle    trianglePoly;
  566.     int            fNum, vPos;
  567.  
  568. /*    Save the current clipping region so that we can restore it later.
  569.     Set our own clipping region, so that we know we have a valid one.
  570.     Also initialize the number of pictures in our global picture
  571.     structure to zero.                                                    */
  572.  
  573.     oldClip = NewRgn();
  574.     GetClip(oldClip);
  575.  
  576.     SetRect(&gGroupieBounds, 0, 0, 150, 150);
  577.     ClipRect(&gGroupieBounds);
  578.     
  579.     gPICTRec.numPICTs = 0;
  580.  
  581.  
  582. /*    Create a picture with a blue square in it.  We set the curPos
  583.     rectangle for all of these pictures to (0, 0, 0, 0) so that
  584.     we don't do any unnecessary erasing the first time they enter
  585.     MoveTheGroupies.                                                    */
  586.  
  587.     gPICTRec.picture[0] = OpenPicture(&gGroupieBounds);
  588.     ForeColor(blueColor);
  589.     PaintRect(&gGroupieBounds);
  590.     ClosePicture();
  591.     SetRect(&gPICTRec.curPos[0], 0, 0, 0, 0);
  592.     ++gPICTRec.numPICTs;
  593.  
  594.  
  595. /*    Create a picture with a red circle in it.                            */
  596.  
  597.     gPICTRec.picture[1] = OpenPicture(&gGroupieBounds);
  598.     ForeColor(redColor);
  599.     PaintOval(&gGroupieBounds);
  600.     ClosePicture();
  601.     SetRect(&gPICTRec.curPos[1], 0, 0, 0, 0);
  602.     ++gPICTRec.numPICTs;
  603.  
  604.  
  605. /*    Create a picture with a green triangle in it.    */
  606.  
  607.     gPICTRec.picture[2] = OpenPicture(&gGroupieBounds);
  608.     ForeColor(greenColor);
  609.  
  610.     trianglePoly = OpenPoly();
  611.     MoveTo(gGroupieBounds.left, gGroupieBounds.bottom);
  612.     LineTo((gGroupieBounds.right - gGroupieBounds.left)/2, gGroupieBounds.top);
  613.     LineTo(gGroupieBounds.right, gGroupieBounds.bottom);
  614.     LineTo(gGroupieBounds.left, gGroupieBounds.bottom);
  615.     ClosePoly();
  616.  
  617.     PaintPoly(trianglePoly);
  618.     KillPoly(trianglePoly);
  619.     ClosePicture();
  620.     SetRect(&gPICTRec.curPos[2], 0, 0, 0, 0);
  621.     ++gPICTRec.numPICTs;
  622.  
  623.  
  624. /*    Create a picture with some text in it.    */
  625.  
  626.     gPICTRec.picture[3] = OpenPicture(&gGroupieBounds);
  627.     ForeColor(blackColor);
  628.  
  629.     GetFNum((ConstStr255Param) "\pTimes", &fNum);
  630.     TextFont(fNum);
  631.     TextSize(12);
  632.     TextFont(bold);
  633.     vPos = gGroupieBounds.top +(gGroupieBounds.bottom - gGroupieBounds.top)/2;
  634.     MoveTo(gGroupieBounds.left +10, vPos +10);
  635.     DrawString((ConstStr255Param) "\pWe are the Groupies!");
  636.     ClosePicture();
  637.     SetRect(&gPICTRec.curPos[3], 0, 0, 0, 0);
  638.     ++gPICTRec.numPICTs;
  639.  
  640.  
  641. /*    Restore the original clipping region.                                */
  642.  
  643.     SetClip(oldClip);
  644.     DisposeRgn(oldClip);
  645. }
  646.  
  647.  
  648. /*    -------------------------------------
  649.     ShowTheGroupies starts the show.
  650.     First, we find the deepest display
  651.     because the groupies are a colorful
  652.     bunch.  Then we create a window and
  653.     some Groupies.  Finally, we jump into
  654.     our main event loop.
  655.     -------------------------------------    */
  656.  
  657. void ShowTheGroupies()
  658. {
  659.     Rect        maxRect, deepRect, wBounds;
  660.     GDHandle    deepGDH;
  661.  
  662. /*    Find the bounds of the deepest device.  We'll use this to determine
  663.     where to put our window.  Passing the maximum enclosing rectangle
  664.     to GetMaxDevice assures that we find the deepest device available.    */
  665.  
  666.     SetRect(&maxRect, -32767, -32767, 32767, 32767);
  667.     deepGDH = GetMaxDevice(&maxRect);
  668.     deepRect = (*deepGDH)->gdRect;
  669.  
  670.  
  671. /*    Create a window for our drawing, offset onto the deepest device.    */
  672.     
  673.     SetRect(&wBounds, 40, 40, 360, 340);
  674.     OffsetRect(&wBounds, wBounds.left +deepRect.left, wBounds.top +deepRect.top);
  675.  
  676.     gTheWindow = NewWindow(nil, &wBounds, (ConstStr255Param) "\pDTS Groupies!", true, noGrowDocProc, (WindowPtr) -1, false, 1234);
  677.     SetPort(gTheWindow);
  678.  
  679.  
  680. /*    Create our pictures to group, then go into the work loop.  This
  681.     loop continually groups the pictures, draws the grouped picture
  682.     in different locations, ungroups the picture, draws the ungrouped
  683.     pictures in different locations and repeats until the user quits.    */
  684.  
  685.     MakeTheGroupies();
  686.     GetGroupieEvents();
  687. }
  688.  
  689.  
  690. /*    -------------------------------------
  691.     main.
  692.     -------------------------------------    */
  693.  
  694. main()
  695. {    
  696.     Handle        menuBar;
  697.  
  698. /*    Initialize the toolbox routines.                                    */
  699.  
  700.     InitGraf(&thePort);
  701.     InitFonts();
  702.     InitWindows();
  703.     InitMenus();
  704.     InitDialogs(nil);
  705.     InitCursor();
  706.  
  707.  
  708. /*    Set up our menubar.                                                    */
  709.  
  710.     menuBar = GetNewMBar(rMenuBar);        /*    Read menus into menu bar    */
  711.     SetMenuBar(menuBar);                /*    and install them.            */
  712.     DisposHandle(menuBar);
  713.     
  714.     AddResMenu(GetMHandle(mApple), 'DRVR');
  715.     DrawMenuBar();
  716.  
  717.  
  718. /*    Initialize the random number seed for our hopping groupies and set
  719.     our quitting flag to false.  Call the routine that runs everything,
  720.     then, quit.                                                            */
  721.  
  722.     GetDateTime((unsigned long *) &randSeed);
  723.     gQuitting = false;
  724.     ShowTheGroupies();
  725. }
  726.